﻿using System;
using System.Collections.Generic;
using System.Linq;
using Babelfish.Node;

namespace Babelfish.Extensions
{
    /// <summary>
    /// Extension methods to be used for traversing Babelfish INode trees
    /// </summary>
    public static class INodeExtensions
    {
        /// <summary>
        /// Finds the specified node using the supplied predicate.
        /// </summary>
        /// <param name="node">The node.</param>
        /// <param name="predicate">The predicate.</param>
        /// <returns>a list of matching nodes</returns>
        public static IEnumerable<INode> Find(this INode node, Func<INode, bool> predicate)
        {
            List<INode> result = new List<INode>();

            foreach (var child in node.ChildNodes)
            {
                if (predicate(child))
                    result.Add(child);

                foreach (var match in child.Find(predicate))
                    result.Add(match);
            }

            return result;
        }

        /// <summary>
        /// Finds the specified node using the supplied predicate.
        /// </summary>
        /// <param name="nodes">The nodes.</param>
        /// <param name="predicate">The predicate.</param>
        /// <returns>a list of matching nodes</returns>
        public static IEnumerable<INode> Find(this IEnumerable<INode> nodes, Func<INode, bool> predicate)
        {
            List<INode> result = new List<INode>();

            if (nodes == null || nodes.Count() == 0)
                return result;

            foreach (var node in nodes)
                foreach (var match in node.Find(predicate))
                    result.Add(match);

            return result;
        }

        /// <summary>
        /// Finds the first matching node or a EmptyNode
        /// </summary>
        /// <param name="nodes">The nodes.</param>
        /// <param name="nodeName">Name of the node.</param>
        /// <returns>a matching INode or a EmptyNode</returns>
        public static INode FindFirst(this IEnumerable<INode> nodes, string nodeName)
        {
            return nodes.Find(nodeName).FirstOrEmpty();
        }

        /// <summary>
        /// Finds the first matching node or a EmptyNode
        /// </summary>
        /// <param name="node">The node.</param>
        /// <param name="nodeName">Name of the node.</param>
        /// <returns>a mathching INode or a EmptyNode</returns>
        public static INode FindFirst(this INode node, string nodeName)
        {
            return node.Find(nodeName).FirstOrEmpty();
        }

        /// <summary>
        /// returns the First INode of the list or a EmptyNode-instance if the list is empty.
        /// </summary>
        /// <param name="list">The list.</param>
        /// <returns>first INode of the supplied list or an EmptyNode</returns>
        public static INode FirstOrEmpty(this IEnumerable<INode> list)
        {
            return list.FirstOrDefault() ?? new EmptyNode();
        }

        /// <summary>
        /// Finds the specified nodes.
        /// </summary>
        /// <param name="nodes">The nodes.</param>
        /// <param name="nodeName">Name of the node.</param>
        /// <returns>a list of matching nodes</returns>
        public static IEnumerable<INode> Find(this IEnumerable<INode> nodes, string nodeName)
        {
            var list = new List<INode>();

            foreach (var node in nodes)
                foreach (var result in node.Find(nodeName))
                    list.Add(result);

            return list;
        }

        /// <summary>
        /// Finds the specified node.
        /// </summary>
        /// <param name="node">The node.</param>
        /// <param name="nodeName">Name of the node.</param>
        /// <returns>a list of matching nodes</returns>
        public static IEnumerable<INode> Find(this INode node, string nodeName)
        {
            return node.Find(n => n.Name == nodeName);
        }

        /// <summary>
        /// Removes this node from its parent node.
        /// </summary>
        /// <param name="node">The node.</param>
        public static INode Remove(this INode node)
        {
            node.Parent.Remove(node);
            return node;
        }

        /// <summary>
        /// Removes the specified node.
        /// </summary>
        /// <param name="node">The node.</param>
        /// <param name="nodeToRemove">The node to remove.</param>
        /// <returns></returns>
        public static INode Remove(this INode node, INode nodeToRemove)
        {
            node.ChildNodes.Remove(nodeToRemove);
            return nodeToRemove;
        }



        /// <summary>
        /// Clones the specified node.
        /// </summary>
        /// <param name="node">The node.</param>
        /// <returns></returns>
        public static INode Clone(this INode node)
        {


            return null;
        }


        /// <summary>
        /// Removes the specified nodes from their parent nodes.
        /// </summary>
        /// <param name="nodes">The nodes.</param>
        public static IEnumerable<INode> Remove(this IEnumerable<INode> nodes)
        {
            var n = nodes.ToList();

            foreach (var node in nodes)
                node.Parent.Remove(node);

            return n;
        }

        /// <summary>
        /// Gets all attributes.
        /// </summary>
        /// <param name="nodes">The nodes.</param>
        /// <returns></returns>
        public static IEnumerable<IAttribute> GetAllAttributes(this IEnumerable<INode> nodes)
        {
            var list = new List<IAttribute>();

            foreach (var node in nodes)
                list.AddRange(node.Attributes);

            return list;
        }
    }
}
